package JavaEE;
import com.sun.source.tree.ContinueTree;

import java.util.*;
/**
 定时器作用:
 经过某一时刻后,执行某任务
 比如(定期清理垃圾,某些备用方案等)
 **/
class MyTimerTask{
    private Runnable runnable;
    private long delay;
    public MyTimerTask(Runnable run,long time){
        runnable = run;
        // 如果我们直接将"多久后执行任务"的time记录下来,会导致没有一个准确的时间
        // 比如:2点的时候,老板让我30分钟后去泡杯茶
        // 我肯定是记:(2:30去泡茶)而肯定不是(30分钟后去泡茶)~

        // 转换成"执行任务的绝对时间戳"
        delay = System.currentTimeMillis() + time;
    }
    public Runnable getRunnable(){
        return runnable;
    }
    public long getDelay(){
        return delay;
    }
}
// 模拟实现简单定时器
// 可能需要存储多个任务,并且需要按照每个任务的"执行时间"来先后进行
// 那么使用PriorityQueue就是一个比较不错的选择
// (但肯定不能只存"任务"或"时间",所以创建MyTimerTask类~)
class MyTimer{
    // 以time创建小根堆
    public Object locker = new Object();
    public static PriorityQueue<MyTimerTask> queue =
            new PriorityQueue<>(Comparator.comparing(t -> t.getDelay()));
    public MyTimer(){
        Thread t = new Thread(() -> {
            while(true) {
                /** 问题1:线程安全问题 -> 加锁!!! **/
                // 1.注意,poll()和offer()为"写"操作,线程不安全
                // 2.offer操作在主线程,poll操作在t线程,不同线程操控同一变量,线程不安全
                synchronized (locker) {
                    /** 问题2:触发"忙等" -> 如果没有任务,就continue **/
                    // 1.这不会解决实际问题,并且还会一直消耗cpu.
                    // 2.频繁加锁,导致schedule可能拿不到锁,触发"线程饿死"
                    /** 线程饿死:某个线程因长期无法获得所需的系统资源(cpu时间片,锁等)而一直无法执行任务 **/
                    try {
                        // while防止"虚假唤醒" - (即没有使用notify,也可能无缘无故的醒来)
                        // java官方说明:底层操作系统或JVM实现可能导致此行为(罕见,但防御性编程)
                        while (queue.isEmpty()) {
                            // continue;
                            // 线程t设为"阻塞状态",并且释放locker锁,等待schedule加入新任务(解决忙等问题)
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        long key = task.getDelay();
                        long now = System.currentTimeMillis();
                        // 如果未到达执行时间,直接continue,同样会触发忙等.
                        // wait进行等待,最长等待时间为"当前时间距离此任务的时间差"
                        /* 为什么用wait而不是sleep? : 每次schedule都要唤醒,因为可能新加入的任务需要更先执行 */
                        while (now < key) {
                            locker.wait(key - now);
                        }
                        queue.poll();
                        task.getRunnable().run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
    }
    public void schedule(Runnable run,long time){
        synchronized (locker) {
            queue.offer(new MyTimerTask(run, time));
            // 此时加入新的任务,queue不为空,唤醒线程t继续干活
            locker.notify();
        }
    }
}
/*
 *         Runnable        和        TimerTask的区别
 * 所属包:  java.lang                 java.util
 * 类/接口: 接口(interface)            抽象类(abstract class)
 * 主要用途:定义可在线程中执行的任务       定义可由Timer调度的延时/周期性任务
 * 线程模型:依赖Thread或线程池执行        由Timer单线程调度
 */
public class MyTimerTest {
    public static void main(String[] args) {
        // 使用"模拟实现的定时器"
        Random random = new Random();
        MyTimer mytimer = new MyTimer();
        for(int i = 0;i < 10;i++){
            int num = i;
            int time = num * random.nextInt(1000);
            mytimer.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行 " + num + " 号任务,要求是" + time + "ms后执行");
                }
            },time);
        }
    }
}
